package com.hucsmn.examsystem.model;

import com.hucsmn.examsystem.common.Problem;
import com.hucsmn.examsystem.server.ExamServerException;

import java.io.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * 题目数据库。
 */
public class ProblemDatabase implements Serializable {
    private static final long serialVersionUID = 289665448719238019L;
    private final Map<Integer, Problem> problems = new HashMap<>();
    private int nextId = 1;

    public ProblemDatabase() {
    }

    /**
     * 导入数据库。
     *
     * @param file 数据库路径。
     * @return 导入的数据库。
     * @throws IOException 数据库读取错误、数据库格式不正确、或数据库状态不一致。
     */
    public static ProblemDatabase load(File file) throws IOException {
        try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
            Object obj = in.readObject();
            if (!(obj instanceof ProblemDatabase)) {
                throw new ClassCastException();
            }
            ProblemDatabase db = (ProblemDatabase) obj;
            if (!db.validate()) {
                throw new ExamServerException("invalid problem database");
            }
            return db;
        } catch (ClassNotFoundException | ClassCastException e) {
            throw new ExamServerException("not a user database");
        }
    }

    /**
     * 通过编号获取题目内容。
     *
     * @param id 题目编号。
     * @return 题目内容，编号不存在时返回 null。
     */
    public synchronized Problem get(int id) {
        Problem problem = problems.get(id);

        if (problem != null) {
            try {
                problem = (Problem) problem.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
                return null;
            }
        }
        return problem;
    }

    /**
     * 录入题目内容。
     *
     * @param problem 待录入题目内容，忽略编号部分。
     * @return 题目编号，录入失败返回 -1。
     */
    public synchronized int add(Problem problem) {
        if (problem == null) {
            throw new NullPointerException();
        }

        try {
            problem = (Problem) problem.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return -1;
        }

        problem.setId(nextId);
        problems.put(nextId, problem);
        return nextId++;
    }

    /**
     * 通过编号删除题目。
     *
     * @param id 题目编号。
     * @return 当题目存在且删除成功时，返回真。
     */
    public synchronized boolean delete(int id) {
        return problems.remove(id) != null;
    }

    /**
     * 修改题目内容。
     *
     * @param id      待修改题目编号。
     * @param problem 新的题目内容，忽略编号部分。
     * @return 编号存在且修改成功时，返回真。
     */
    public synchronized boolean update(int id, Problem problem) {
        if (problem == null) {
            throw new NullPointerException();
        }

        try {
            problem = (Problem) problem.clone();
            problem.setId(id);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return false;
        }

        return problems.replace(id, problem) != null;
    }

    /**
     * 获取所有题目。
     *
     * @return 题目列表。
     */
    public synchronized Problem[] select() {
        return problems.values().toArray(new Problem[0]);
    }

    /**
     * 随机抽取一定数目的题目。
     *
     * @param count 最大题目数量。
     * @return 不重复的随机题目列表。当题库题量比指定数目小时，返回题目数将小于指定数目。
     */
    public synchronized Problem[] select(int count) {
        int i = 0;
        Problem[] result = new Problem[Math.min(this.problems.size(), count)];
        Random rng = new Random();
        while (i < result.length) {
            int id = rng.nextInt(nextId);
            Problem problem = get(id);
            if (problem == null || Arrays.stream(result, 0, i).anyMatch(p -> p.getId() == id)) {
                continue;
            }
            result[i++] = problem;
        }

        return result;
    }

    /**
     * 导出数据库。
     *
     * @param file 数据库路径。
     * @throws IOException 数据库写入异常。
     */
    public synchronized void save(File file) throws IOException {
        try (ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
            out.writeObject(this);
            out.flush();
        }
    }

    /**
     * 检测刚刚导入的数据库，内部状态是否一致。
     *
     * @return 状态一致返回真。
     */
    private boolean validate() {
        //TODO: validate internal state.
        return true;
    }
}
